﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;

using CryForLua_wpf_AvEd.UserPreferences;
using CryForLua_wpf_AvEd.CodeAnalysing;

namespace CryForLua_wpf_AvEd.CodeCompletion
{
    public class CodeTable
    {
        public static CodeTable _codeTableInstance;

        /// <summary>
        /// Singleton ftw
        /// </summary>
        private CodeTable()
        {

            if (_gblFncTbl == null)
            {
                _gblFncTbl = new Dictionary<string, LuaFunctionTableBlah>();
            }

            _gblFncTbl.Add("AI", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Entity", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Particle", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Physics", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Script", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Sound", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Action", new LuaFunctionTableBlah());
            _gblFncTbl.Add("ActionMapManager", new LuaFunctionTableBlah());
            _gblFncTbl.Add("ActorSystem", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Inventory", new LuaFunctionTableBlah());
            _gblFncTbl.Add("ItemSystem", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Network", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Movie", new LuaFunctionTableBlah());
            _gblFncTbl.Add("Global_scope", new LuaFunctionTableBlah());
        }

        public static CodeTable Instance
        {
            get
            {

                lock (typeof(CodeTable))
                {
                    if (_codeTableInstance == null)
                        _codeTableInstance = new CodeTable();

                    return _codeTableInstance;
                }
            }
        }


        /// <summary>
        /// Represents a LUA function
        /// </summary>
        public class LuaFunction
        {
            public LuaFunction() : this("", "", "", new List<LuaParameter>())
            {
            }

            public LuaFunction(string sName, string sDesc, string sRetVal, List<LuaParameter> lParamsList)
            {
                name = sName;
                description = sDesc;
                returnValue = sRetVal;
                paramList = lParamsList;
            }

            public LuaFunction(LuaFunction function)
            {
                name = function.name;
                description = function.description;
                returnValue = function.returnValue;
                paramList = function.paramList;
              
            }
            public LuaFunction(string sName, string sDescription, string sReturnValue) 
                : this(sName, sDescription, sReturnValue, new List<LuaParameter>())
            {
               
            }

            /// <summary>
            /// Builds a LuaFunction structure out of the full function head
            /// </summary>
            /// <param name="sFullName">Example DoStuff(entityID, param, param1)</param>
            public static LuaFunction BuildLuaFunction(string sFullName)
            {
                // Catch the garbage
                if (!sFullName.Contains('(') || !sFullName.Contains(')'))
                    return null;

                if (sFullName.Contains(','))
                {
                    LuaFunction returnMe = new LuaFunction();
                    returnMe.name = sFullName;

                    int commaPos = 0;

                    // Extract the parameters
                    while (sFullName.IndexOf(',', commaPos) != -1)
                    {
                        commaPos = sFullName.IndexOf(',', commaPos); // Yes double checking is pretty performance intense

                        string choppedName = sFullName.Substring(0, commaPos);

                        int paramStart = (choppedName.LastIndexOf(',') != -1) ? choppedName.LastIndexOf(',') + 1 : choppedName.LastIndexOf('(') + 1;

                        string paramName = choppedName.Substring(paramStart, commaPos - paramStart);

                        paramName = paramName.Trim(' ');

                        returnMe.paramList.Add(new LuaParameter(paramName, ""));

                        commaPos += 1;
                    }

                    string finalParamName = sFullName.Substring(commaPos, sFullName.Length - 1 - commaPos);
                    finalParamName = finalParamName.Trim(' ');

                    returnMe.paramList.Add(new LuaParameter(finalParamName, ""));

                    return returnMe;
                }
                else
                {
                    LuaFunction returnMe = new LuaFunction();

                    // No commata so there are no or just 1 parameter
                    // Keep the parenthesises so user sees that the function really doesn't have parameters :D
                    if (sFullName[sFullName.Length - 2] != '(')
                    {
                        int parpos = sFullName.LastIndexOf('(');
                        string paramName = sFullName.Substring(parpos + 1, sFullName.Length - 1 - parpos - 1);
                        returnMe.name = sFullName;
                        returnMe.paramList.Add(new LuaParameter(paramName, "No description available"));

                    }

                    returnMe.name = sFullName;
                    return returnMe;
                }
            }

            public LuaParameter GetParamByName(string paramName)
            {
                foreach (LuaParameter param in paramList)
                {
                    if (param.name == paramName)
                    {
                        return param;
                    }
                }

                string message = String.Format("Couldn't find parameter {0} in function {1}!", paramName, this.name);
                MessageBox.Show(message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }

            public void DeleteParamByName(string name)
            {
                paramList.Remove(GetParamByName(name));
            }

            public void ReplaceParamByName(string name, LuaParameter replacer)
            {
                paramList[paramList.IndexOf(GetParamByName(name))] = replacer;
            }

            /// <summary>
            /// Replaces all members of the function with those of replacer
            /// </summary>
            /// <param name="replacer">New function to replace old one</param>
            public void ReplaceMyStats(LuaFunction replacer)
            {
                name = replacer.name;
                description = replacer.description;
                returnValue = replacer.returnValue;

                paramList = replacer.paramList;
            }

            public string name;
            public string description;
            public string returnValue;
            public List<LuaParameter> paramList;
            public LuaCodeBlock m_codeBlock;
           
        }

        /// <summary>
        /// Represents a parameter of a function
        /// </summary>
        public class LuaParameter
        {
            public LuaParameter()
            {
                name = "";
                description = "";
            }

            public LuaParameter(string sName, string sDesc)
            {
                name = sName;
                description = sDesc;

                bIsFlagNotParam = true;
                // Doesn't work, replace with smth else
                for (int i = 0; i < name.Length; ++i)
                {
                    if (!Char.IsUpper(name[i]))
                        bIsFlagNotParam = false;
                }
            }

            public string name;  // contains the type too
            public string description;
            public bool bIsFlagNotParam; // Ye lazy but oh well
        }

        /// <summary>
        /// All functions of a specific kind...(AI, Entity etc.)
        /// </summary>
        public class LuaFunctionTableBlah
        {
            public LuaFunctionTableBlah()
            {
                classFunctions = new List<LuaFunction>();
            }

            public LuaFunctionTableBlah(LuaFunctionTableBlah tbl)
            {
                classFunctions = tbl.classFunctions;
            }
            public LuaFunctionTableBlah(List<LuaFunction> functionList)
            {
                classFunctions = functionList;
            }

            public void RemoveFunctionByName(string name)
            {
                foreach (LuaFunction curFunc in classFunctions)
                {
                    if (curFunc.name == name)
                    {
                        classFunctions.Remove(curFunc);
                        return;
                    }
                }

                // Should never get here

            }

            public void ReplaceFunction(string funName, LuaFunction replacer)
            {
                foreach (LuaFunction curFunc in classFunctions)
                {
                    if (curFunc.name == funName)
                    {
                        int toReplace = classFunctions.IndexOf(curFunc);

                        classFunctions[toReplace] = replacer;
                    }
                }
            }
            public List<LuaFunction> classFunctions;
        }

        public static LuaFunctionTableBlah GetFunctionTableByClassName(string name)
        {
            bool succes;
            LuaFunctionTableBlah table = new LuaFunctionTableBlah();

            succes = _gblFncTbl.TryGetValue(name, out table);

            if (succes)
                return table;
            else
            {
                string message = String.Format("GetFunctionTableByClassName: couldn't find class \"{0}\" in global function table!", name);


                MessageBox.Show(message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }

        }

        public static LuaFunctionTableBlah GetFunctionTableByClassName(string name, ref Dictionary<string, LuaFunctionTableBlah> dictionary)
        {
            bool succes;
            LuaFunctionTableBlah table = new LuaFunctionTableBlah();

            succes = dictionary.TryGetValue(name, out table);

            if (succes)
                return table;
            else
            {
                string message = String.Format("GetFunctionTableByClassName: couldn't find class \"{0}\" in global function table!", name);


                MessageBox.Show(message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
        }

        public static LuaFunctionTableBlah GetFunctionTableByClassNameCreateIfEmpty(string name)
        {

            bool succes;
            LuaFunctionTableBlah table = new LuaFunctionTableBlah();

            succes = _gblFncTbl.TryGetValue(name, out table);

            if (succes)
                return table;
            else
                _gblFncTbl.Add(name, new LuaFunctionTableBlah());

            succes = _gblFncTbl.TryGetValue(name, out table);

            if (succes)
            {
                return table;
            }
            else
            {
                string message = String.Format("GetFunctionTableByClassName: couldn't find class \"{0}\" in global function table!", name);


                MessageBox.Show(message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }


        }

        public static LuaFunction GetFunctionByClassAndName(string className, string functionName, ref Dictionary<string, LuaFunctionTableBlah> dictionary)
        {
            LuaFunctionTableBlah tbl = new LuaFunctionTableBlah();

            bool succes = false;

            succes = dictionary.TryGetValue(className, out tbl);

            if (succes)
            {
                foreach (LuaFunction curFunc in tbl.classFunctions)
                {
                    if (curFunc.name == functionName)
                    {
                        return curFunc;
                    }
                }
            }

            return null;
        }

        public static LuaFunction GetFunctionByClassAndName(string className, string functionName)
        {
            LuaFunctionTableBlah tbl = new LuaFunctionTableBlah();

            bool succes = false;

            succes = TABLE.TryGetValue(className, out tbl);

            if (succes)
            {
                foreach (LuaFunction curFunc in tbl.classFunctions)
                {
                    if (curFunc.name == functionName)
                    {
                        return curFunc;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Searches the whole table of functions for any appearance of sFunctionName
        /// </summary>
        /// <param name="sFunctionName">The name or part of the name to be searched for</param>
        /// <returns></returns>
        public static bool DoesTableContainFunction(string sFunctionName)
        {
            foreach (string key in TABLE.Keys)
            {
                LuaFunctionTableBlah table;
                TABLE.TryGetValue(key, out table);
                
                foreach (LuaFunction function in table.classFunctions)
                {
                    return function.name.Contains(sFunctionName);
                }
            }

            return false;
        }


        /// <summary>
        /// This holds all functions
        /// </summary>
        public void InitGblFncTbl()
        {

        }

        private static Dictionary<string, LuaFunctionTableBlah> _gblFncTbl;

        public static Dictionary<string, LuaFunctionTableBlah> TABLE
        {
            get
            {
                return _gblFncTbl;
            }
        }

        public void SaveGlobalTableToFile()
        {
            XmlTextWriter writer = new XmlTextWriter(Application.UserAppDataPath.ToString() + "\\functionTable.xml", System.Text.Encoding.UTF8);
            writer.Formatting = Formatting.Indented;
            writer.WriteStartDocument(false);

            writer.WriteComment("This holds all the known functions");

            //START globalTable
            writer.WriteStartElement("globalTable");

            Dictionary<string, LuaFunctionTableBlah>.KeyCollection keys = _gblFncTbl.Keys;

            string[] keyWords = new string[keys.Count];
            keys.CopyTo(keyWords, 0);
            int functionCounter = 0;
            int paramCounter = 0;

            // Now the class function tables
            foreach (string className in keyWords)
            {
                //START classname
                writer.WriteStartElement(className);

                List<LuaFunction> functionTable = GetFunctionTableByClassName(className).classFunctions;

                // Now write down each functions params
                foreach (LuaFunction classFunction in functionTable)
                {
                    //START lua classfunction
                    writer.WriteStartElement("f" + functionCounter.ToString());

                    #region Name of func
                    writer.WriteStartAttribute("functionName");
                    writer.WriteValue(classFunction.name);
                    writer.WriteEndAttribute();
                    #endregion

                    #region Desc of func
                    writer.WriteStartAttribute("functionDesc");
                    writer.WriteValue(classFunction.description);
                    writer.WriteEndAttribute();
                    #endregion

                    #region Return value of func
                    writer.WriteStartAttribute("returnValue");
                    writer.WriteValue(classFunction.returnValue);
                    writer.WriteEndAttribute();
                    #endregion

                    #region Parameterlist of function

                    //START lua parameter list
                    writer.WriteStartElement("functionParamList");

                    List<LuaParameter> functionParameterList = classFunction.paramList;
                    if (functionParameterList != null)
                    {

                        foreach (LuaParameter parameter in functionParameterList)
                        {
                            //START param
                            writer.WriteStartElement("p" + paramCounter.ToString());

                            // param name
                            writer.WriteStartAttribute("paramName");
                            writer.WriteValue(parameter.name);
                            writer.WriteEndAttribute();

                            // param desc
                            writer.WriteStartAttribute("paramDesc");
                            writer.WriteValue(parameter.description);
                            writer.WriteEndAttribute();


                            //END param
                            writer.WriteEndElement();

                            paramCounter++;
                        }
                    }

                    // END lua parameter list
                    writer.WriteEndElement();

                    #endregion



                    //END lua classfunction
                    writer.WriteEndElement();

                    paramCounter = 0;

                    functionCounter++;
                }

                //END classname
                writer.WriteEndElement();

                functionCounter = 0;
            }

            //Lazy prevention of reader skipping last class
            writer.WriteStartElement("_skipMe");
            writer.WriteEndElement();

            //End globalTable
            writer.WriteEndElement();

            writer.Flush();
            writer.Close();

        }

        public void ReadGlobalTableFromFile()
        {
            string[] dirs = Directory.GetFiles(Application.UserAppDataPath, "functionTable.xml", SearchOption.AllDirectories);

            if (dirs.Length == 0)
            {
                string message = ("Failed to load function table from file! Please update the sriptbind library! Would you like to update now? "
                    + "WARNING: all custom scriptbind entries will be lost!");
                DialogResult result = MessageBox.Show(message, "ERROR", MessageBoxButtons.YesNo, MessageBoxIcon.Error);

                if (result == DialogResult.Yes)
                {
                    AutoCompletitionManager manager = new AutoCompletitionManager();
                    UserPreferencesWindow preferences = new UserPreferencesWindow();


                    // TODO
                    // Make manager wait for User Settings being saved
                    preferences.ShowDialog();


                    manager.SearchDirectories();
                    SaveGlobalTableToFile();

                }
                else return;
            }

            XmlTextReader reader = new XmlTextReader(Application.UserAppDataPath.ToString() + "\\functionTable.xml");



            //reader.Read();


            foreach (string key in _gblFncTbl.Keys)
            {
                GetFunctionTableByClassName(key).classFunctions.Clear();
            }



            bool firstItem = true;

            string functionTableName = "";
            string functionName = "";
            string functionDescription = "";
            string functionReturnValue = "";
            List<CodeTable.LuaParameter> functionParamList = new List<LuaParameter>();
            LuaFunction currentFunction = new LuaFunction();

            while (reader.Read())
            {
                // reader.MoveToElement();

                switch (reader.NodeType)
                {
                    case (XmlNodeType.Element):
                        {

                            // Set the current table we're in
                            // Here the first class name so we have a starting point
                            if (reader.Depth == 1 && firstItem == true)
                            {
                                functionTableName = reader.Name;
                                if (reader.Name == "ItemSystem")
                                {
                                }
                                firstItem = false;
                            }


                            // We're inside a function 
                            if (reader.Depth >= 2)
                            {
                                // The function itself
                                if (reader.Depth == 2)
                                {
                                    currentFunction.paramList = functionParamList;
                                    if (currentFunction.name != "")
                                    {
                                        LuaFunctionTableBlah tbl = CodeTable.GetFunctionTableByClassNameCreateIfEmpty(functionTableName);

                                        if (CodeTable.GetFunctionByClassAndName(functionTableName, currentFunction.name, ref _gblFncTbl) == null)
                                        {
                                            tbl.classFunctions.Add(currentFunction);
                                        }

                                    }
                                    currentFunction = new LuaFunction();
                                    functionParamList = new List<LuaParameter>();

                                    reader.MoveToAttribute("functionName");
                                    functionName = reader.ReadContentAsString();
                                    reader.MoveToAttribute("functionDesc");
                                    functionDescription = reader.ReadContentAsString();
                                    reader.MoveToAttribute("returnValue");
                                    functionReturnValue = reader.ReadContentAsString();

                                    currentFunction.name = functionName;
                                    currentFunction.description = functionDescription;
                                    currentFunction.returnValue = functionReturnValue;


                                }

                                // FunctionParams
                                if (reader.Depth == 4)
                                {
                                    reader.MoveToAttribute("paramName");
                                    string bar = reader.ReadContentAsString();
                                    reader.MoveToAttribute("paramDesc");
                                    string fudge = reader.ReadContentAsString();

                                    LuaParameter param = new LuaParameter(bar, fudge);

                                    functionParamList.Add(param);
                                }

                            }

                            // Set the current table we're in
                            // All non first class names get set AFTER the last function
                            // of the previous classname has been processed.
                            // Otherwise the last function would end up as first in the new className
                            if (reader.Depth == 1 && firstItem == false)
                            {
                                FlushFunctionList(ref currentFunction, functionTableName);
                                functionTableName = reader.Name;
                            }


                        } break;
                }
            }

            reader.Close();


        }

        void FlushFunctionList(ref LuaFunction functionToFlush, string lastClassName)
        {
            if (functionToFlush.name == "")
                return;

            LuaFunction function = new LuaFunction(functionToFlush);
            CodeTable.GetFunctionTableByClassNameCreateIfEmpty(lastClassName).classFunctions.Add(function);
            functionToFlush.name = "";
        }
    }
}
